前言
在分享适配刘海屏之前,先吐槽一波苹果。居然搞了个畸形屏幕,如果乔大爷还在,这种产品还能问世吗?
1.解决方案思路
通过上图我们不难看出,在刘海屏/圆角屏手机,为保证UI视图能完整展示,我们应通过计算,获取到可用区域的宽高(蓝色区域),并且尽量避免我们的view/window超出此区域,特别注意交互事件。这里有个点要注意:在Android P之后,状态栏区域如果要显示window,必须在Mainfest中添加权限。
2.Android P之前的刘海屏适配
在P之前,国产厂商在Android P之前(基本都是Android O)就用上了高档大气上档次的刘海屏,所以,这也造就了各大厂商在Android P之前的解决方案百花齐放。下面,我们来看下主流厂商:华为、vivo、OPPO、小米等所提供的方案。
各手机厂商官方文档:
OPPO:https://open.oppomobile.com/wiki/doc#id=10159
VIVO:https://dev.vivo.com.cn/doc/document/info?id=103
HUAWEI:https://mini.eastday.com/bdmip/180411011257629.html
这里说挖孔屏是状态栏高度的两倍
https://arstechnica.com/gadgets/2017/09/essential-phone-review-impressive-for-a-new-company-but-not-competitive/
然而不同厂商的挖孔高度明显参差不齐,所以使用获取状态栏高度来决定刘海高度这种方案是不可行的。
2.1 华为
2.1.1 使用刘海区显示
使用新增的meta-data属性android.notch_support。在应用的AndroidManifest.xml中增加meta-data属性,此属性不仅可以针对Application生效,也可以对Activity配置生效。如下所示:
复制代码
• 对Application生效,意味着该应用的所有页面,系统都不会做竖屏场景的特殊下移或者是横屏场景的右移特殊处理。
• 对Activity生效,意味着可以针对单个页面进行刘海屏适配,设置了该属性的Activity系统将不会做特殊处理。
2.1.2 是否有刘海屏
通过以下代码即可知道华为手机上是否有刘海屏了,true为有刘海,false则没有。
public static boolean hasNotchAtHuawei(Context context) {
boolean ret = false;
try {
ClassLoader classLoader = context.getClassLoader();
Class HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil");
Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
ret = (boolean) get.invoke(HwNotchSizeUtil);
} catch (ClassNotFoundException e) {
Log.e("Notch", "hasNotchAtHuawei ClassNotFoundException");
} catch (NoSuchMethodException e) {
Log.e("Notch", "hasNotchAtHuawei NoSuchMethodException");
} catch (Exception e) {
Log.e("Notch", "hasNotchAtHuawei Exception");
} finally {
return ret;
}
}
2.1.3 刘海尺寸
华为提供了接口获取刘海的尺寸,如下:
//获取刘海尺寸:width、height
//int[0]值为刘海宽度 int[1]值为刘海高度
public static int[] getNotchSizeAtHuawei(Context context) {
int[] ret = new int[]{0, 0};
try {
ClassLoader cl = context.getClassLoader();
Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
Method get = HwNotchSizeUtil.getMethod("getNotchSize");
ret = (int[]) get.invoke(HwNotchSizeUtil);
} catch (ClassNotFoundException e) {
Log.e("Notch", "getNotchSizeAtHuawei ClassNotFoundException");
} catch (NoSuchMethodException e) {
Log.e("Notch", "getNotchSizeAtHuawei NoSuchMethodException");
} catch (Exception e) {
Log.e("Notch", "getNotchSizeAtHuawei Exception");
} finally {
return ret;
}
}
2.2 vivo
vivo在设置–显示与亮度–第三方应用显示比例中可以切换是否全屏显示还是安全区域显示。
2.2.1 是否有刘海屏
public static final int VIVO_NOTCH = 0x00000020;//是否有刘海
public static final int VIVO_FILLET = 0x00000008;//是否有圆角
public static boolean hasNotchAtVoio(Context context) {
boolean ret = false;
try {
ClassLoader classLoader = context.getClassLoader();
Class FtFeature = classLoader.loadClass("android.util.FtFeature");
Method method = FtFeature.getMethod("isFeatureSupport", int.class);
ret = (boolean) method.invoke(FtFeature, VIVO_NOTCH);
} catch (ClassNotFoundException e) {
Log.e("Notch", "hasNotchAtVoio ClassNotFoundException");
} catch (NoSuchMethodException e) {
Log.e("Notch", "hasNotchAtVoio NoSuchMethodException");
} catch (Exception e) {
Log.e("Notch", "hasNotchAtVoio Exception");
} finally {
return ret;
}
}
2.2.2 刘海尺寸
vivo不提供接口获取刘海尺寸,目前vivo的刘海宽为100dp,高为27dp。
2.3 OPPO
OPPO目前在设置 – 显示 – 应用全屏显示 – 凹形区域显示控制,里面有关闭凹形区域开关。
2.3.1 是否有刘海屏
public static boolean hasNotchInScreenAtOPPO(Context context) {
return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
}
2.3.2 刘海尺寸
OPPO不提供接口获取刘海尺寸,目前其有刘海屏的机型尺寸规格都是统一的。不排除以后机型会有变化。其显示屏宽度为1080px,高度为2280px。刘海区域则都是宽度为324px, 高度为80px。
2.4 小米
2.4.1 是否有刘海屏
系统增加了 property ro.miui.notch,值为1时则是 Notch 屏手机。
手头上没有小米8的手机,暂时没法验证,这里就不贴代码了,免得误导大家。后面测试过再放出来。
2.4.2 刘海尺寸
小米的状态栏高度会略高于刘海屏的高度,因此可以通过获取状态栏的高度来间接避开刘海屏,获取状态栏的高度代码如下:
public static int getStatusBarHeight(Context context) {
int statusBarHeight = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
}
return statusBarHeight;
}
其他手机也可以通过这个方法来间接避开刘海屏,但是有可能有些手机的刘海屏高度会高于状态栏的高度,所以这个方法获取到的结果并不一定安全。
3.Android P中的刘海屏适配
3.1 Google对刘海屏的支持介绍
Android P 支持最新的全面屏以及为摄像头和扬声器预留空间的凹口屏幕。 通过全新的 DisplayCutout 类,可以确定非功能区域的位置和形状,这些区域不应显示内容。 要确定这些凹口屏幕区域是否存在及其位置,请使用 getDisplayCutout() 函数。
3.2 设置凹口屏幕显示模式
全新的窗口布局属性 layoutInDisplayCutoutMode 让您的应用可以为设备凹口屏幕周围的内容进行布局。 您可以将此属性设为下列值之一:
• LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
• LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
• LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
模式 模式说明
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 只有当DisplayCutout完全包含在系统栏中时,才允许窗口延伸到DisplayCutout区域。 否则,窗口布局不与DisplayCutout区域重叠。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 该窗口决不允许与DisplayCutout区域重叠。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 该窗口始终允许延伸到屏幕短边上的DisplayCutout区域。
例子:
var lp = window.attributes
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
window.attributes = lp
3.3 Android P中凹口屏幕相关接口
注意,以下接口都是要Build.VERSION.SDK_INT >= 28才能调用到。
DisplayCutout类接口
主要用于获取凹口位置和安全区域的位置等。主要接口如下所示:
方法 接口说明
getBoundingRects() 返回Rects的列表,每个Rects都是显示屏上非功能区域的边界矩形。
getSafeInsetLeft () 返回安全区域距离屏幕左边的距离,单位是px。
getSafeInsetRight () 返回安全区域距离屏幕右边的距离,单位是px。
getSafeInsetTop () 返回安全区域距离屏幕顶部的距离,单位是px。
getSafeInsetBottom() 返回安全区域距离屏幕底部的距离,单位是px。